﻿using Microsoft.Owin.Security;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sustainalytics.FileManager;
using Sustainalytics.LongRunningTaskManager.Interface;
using Sustainalytics.LongRunningTaskManager.Tests;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading;
using System.Web.Http;

namespace Sustainalytics.LongRunningTaskManager.Service.Tests
{
    [TestClass]
    public class LongRunningTaskControllerTests
    {
        [TestInitialize]
        public void TestInitialize()
        {
        }


        #region POST

        [TestMethod]
        public void Post_AddTask()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskInt().Type;

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getStatus: true).Result;

            statusRes.EnsureSuccessStatusCode();

            var status = statusRes.Content.ReadAsAsync<LongRunningTaskInstanceStatus>().Result;

            Assert.IsNotNull(status, "Status is null.");
            Assert.IsTrue(status.TaskInstanceStatus == LongRunningTaskInstanceStatusEnum.Finished, "Task not finished.");
        }

        [TestMethod]
        public void Post_AddTask_Upload_Stream()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskUpload().Type;

            string fileName = "file.txt";

            string mimeType = "application/text";

            string filePath = Path.Combine(Environment.CurrentDirectory, @"TestFiles\file.txt");

            // add file content
            ctrl.Request.Content = new StreamContent(File.OpenRead(filePath));            

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            // remove result directory
            var targetDir = Path.Combine(Environment.CurrentDirectory, "TaskResult");
            if (Directory.Exists(targetDir))
            {
                Directory.Delete(targetDir, true);
            }

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType,
                IsUpload = true,
                FileName = fileName,
                MimeType = mimeType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getStatus: true).Result;

            statusRes.EnsureSuccessStatusCode();

            var status = statusRes.Content.ReadAsAsync<LongRunningTaskInstanceStatus>().Result;

            Assert.IsNotNull(status, "Status is null.");
            Assert.IsTrue(status.TaskInstanceStatus == LongRunningTaskInstanceStatusEnum.Finished, "Task not finished.");

            string targetPath = Path.Combine(Environment.CurrentDirectory, "TaskResult", username, "file.txt");

            Assert.IsTrue(File.ReadAllBytes(filePath).SequenceEqual(File.ReadAllBytes(targetPath)), "Incorrect file contents");
        }

        [TestMethod]
        public void Post_AddTask_Upload_Multipart()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskUpload().Type;

            string fileName = "file.txt";

            string mimeType = "application/text";

            string filePath = Path.Combine(Environment.CurrentDirectory, @"TestFiles\file.txt");

            // add file content
            var multipartContent = new MultipartFormDataContent();
            var fileContent1 = new StreamContent(File.OpenRead(filePath));
            multipartContent.Add(fileContent1, "fileContent1", "file.txt");
            ctrl.Request.Content = multipartContent;

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            // remove result directory
            var targetDir = Path.Combine(Environment.CurrentDirectory, "TaskResult");
            if (Directory.Exists(targetDir))
            {
                Directory.Delete(targetDir, true);
            }

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType,
                IsUpload = true,
                FileName = fileName,
                MimeType = mimeType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getStatus: true).Result;

            statusRes.EnsureSuccessStatusCode();

            var status = statusRes.Content.ReadAsAsync<LongRunningTaskInstanceStatus>().Result;

            Assert.IsNotNull(status, "Status is null.");
            Assert.IsTrue(status.TaskInstanceStatus == LongRunningTaskInstanceStatusEnum.Finished, "Task not finished.");

            string targetPath = Path.Combine(Environment.CurrentDirectory, "TaskResult", username, "file.txt");

            Assert.IsTrue(File.ReadAllBytes(filePath).SequenceEqual(File.ReadAllBytes(targetPath)), "Incorrect file contents");
        }

        #endregion

        #region DELETE

        [TestMethod]
        public void Delete()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
                DelayMs = 1000
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskInt().Type;

            string username = "myUser";

            // set auth ticket
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);


            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            Thread.Sleep(100);


            var deleteRes = ctrl.Delete(taskInstanceId: taskInstanceId).Result;

            deleteRes.EnsureSuccessStatusCode();

            Thread.Sleep(100);


            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getStatus: true).Result;

            Assert.IsTrue(statusRes.StatusCode == HttpStatusCode.NotFound);
        }

        [TestMethod]
        public void Delete_NotFound()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string username = "myUser";

            // set auth ticket
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var deleteRes = ctrl.Delete(taskInstanceId: "0").Result;

            Assert.IsTrue(deleteRes.StatusCode == HttpStatusCode.NotFound);
        }

        #endregion

        #region GET

        [TestMethod]
        public void Get_OverallStatus()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskInt().Type;

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(overallStatus: true).Result;

            statusRes.EnsureSuccessStatusCode();

            var status = statusRes.Content.ReadAsAsync<IEnumerable<LongRunningTaskInstanceStatus>>().Result;

            Assert.IsNotNull(status, "Status is null.");
            Assert.IsTrue(status.Count() == 1);
        }

        [TestMethod]
        public void Get_Status()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskInt().Type;

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getStatus: true).Result;

            statusRes.EnsureSuccessStatusCode();

            var status = statusRes.Content.ReadAsAsync<LongRunningTaskInstanceStatus>().Result;

            Assert.IsNotNull(status, "Status is null.");
            Assert.IsTrue(status.TaskInstanceStatus == LongRunningTaskInstanceStatusEnum.Finished, "Task not finished.");
        }

        [TestMethod]
        public void Get_Status_NotFound()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string username = "myUser";

            // set auth ticket
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var getRes = ctrl.Get(taskInstanceId: "0", getStatus: true).Result;

            Assert.IsTrue(getRes.StatusCode == HttpStatusCode.NotFound);
        }

        [TestMethod]
        public void Get_Result_Object()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskObject().Type;

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getResult: true).Result;

            statusRes.EnsureSuccessStatusCode();

            var data = statusRes.Content.ReadAsAsync<int>().Result;

            Assert.IsTrue(data == (int)MyTaskObject.ReturnValue.Data);
        }

        [TestMethod]
        public void Get_Result_FileInMemToFileManager()
        {
            // remove result directory
            var targetDir = Path.Combine(Environment.CurrentDirectory, "TaskResult");
            if (Directory.Exists(targetDir))
            {
                Directory.Delete(targetDir, true);
            }

            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskFileInMem().Type;

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getResult: true).Result;

            statusRes.EnsureSuccessStatusCode();

            PushStreamContent content = statusRes.Content as PushStreamContent;

            var bytes = content.ReadAsByteArrayAsync().Result;

            Assert.IsTrue(bytes.SequenceEqual(MyTaskFileInMem.ReturnValue.Contents));
            Assert.IsTrue(statusRes.Content.Headers.ContentDisposition != null && statusRes.Content.Headers.ContentDisposition.FileName == MyTaskFileInMem.ReturnValue.FileName);
        }

        [TestMethod]
        public void Get_Result_FileOnDiskToFileManager()
        {
            // remove result directory
            var targetDir = Path.Combine(Environment.CurrentDirectory, "TaskResult");
            if (Directory.Exists(targetDir))
            {
                Directory.Delete(targetDir, true);
            }

            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskFileOnDisk().Type;

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getResult: true).Result;

            statusRes.EnsureSuccessStatusCode();

            PushStreamContent content = statusRes.Content as PushStreamContent;

            var bytes = content.ReadAsByteArrayAsync().Result;

            byte[] fsBytes = File.ReadAllBytes(MyTaskFileOnDisk.ReturnValue.FilePath);

            Assert.IsTrue(bytes.SequenceEqual(fsBytes));
            Assert.IsTrue(statusRes.Content.Headers.ContentDisposition != null && statusRes.Content.Headers.ContentDisposition.FileName == MyTaskFileInMem.ReturnValue.FileName);
        }

        [TestMethod]
        public void Get_Result_FileManager()
        {
            // remove result directory
            var targetDir = Path.Combine(Environment.CurrentDirectory, "TaskResult");
            if (Directory.Exists(targetDir))
            {
                Directory.Delete(targetDir, true);
            }

            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string title = "myTask";

            var param = new MyTaskParam
            {
            };

            string paramJson = param.ToJson();

            var taskType = new MyTaskFileManager().Type;

            // set auth ticket
            string username = "myUser";
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var postRes = ctrl.Post(new LongRunningTaskPostData
            {
                Title = title,
                ParamJson = paramJson,
                TaskType = taskType
            }).Result;

            postRes.EnsureSuccessStatusCode();

            string taskInstanceId = postRes.Content.ReadAsAsync<string>().Result;

            var asyncInfo = ctrl.TaskManager.GetTaskAsyncInfo(ticket, taskInstanceId);
            asyncInfo.WaitForStatus(LongRunningTaskInstanceStatusEnum.Finished, 2000);

            var statusRes = ctrl.Get(taskInstanceId: taskInstanceId, getResult: true).Result;

            statusRes.EnsureSuccessStatusCode();

            PushStreamContent content = statusRes.Content as PushStreamContent;

            var bytes = content.ReadAsByteArrayAsync().Result;

            // get file from file manager
            var hostConfig = LongRunningTaskManagerHostConfiguration.Current;
            var fileManager = new DiskFileManager(hostConfig.DiskFileManagerBaseDirectory);
            FileContents fileContents = fileManager.GetFile(MyTaskFileManager.ReturnValue.FilePath);
            MemoryStream ms = new MemoryStream();
            using (var srcStream = fileContents.StreamAccessor())
            {
                srcStream.CopyTo(ms);                
            }
            ms.Seek(0, SeekOrigin.Begin);

            Assert.IsTrue(bytes.SequenceEqual(ms.ToArray()));
            Assert.IsTrue(statusRes.Content.Headers.ContentDisposition != null && statusRes.Content.Headers.ContentDisposition.FileName == MyTaskFileInMem.ReturnValue.FileName);
        }

        [TestMethod]
        public void Get_Result_NotFound()
        {
            List<LongRunningTaskInstanceSnapshot> lst = new List<LongRunningTaskInstanceSnapshot>();

            var ctrl = CreateController(lst);

            string username = "myUser";

            // set auth ticket
            var ticket = CreateAuthenticationTicket(username);
            ctrl.User = new ClaimsPrincipal(ticket.Identity);

            var getRes = ctrl.Get(taskInstanceId: "0", getResult: true).Result;

            Assert.IsTrue(getRes.StatusCode == HttpStatusCode.NotFound);
        }

        #endregion


        private LongRunningTaskController CreateController(List<LongRunningTaskInstanceSnapshot> lst)
        {
            var hostConfig = LongRunningTaskManagerHostConfiguration.Current;

            // create task instance instance repository
            var taskInstanceSnapshotRepo = CreateTaskInstanceSnapshotRepo(lst);

            // create task manager
            var taskManager = CreateTaskManager(taskInstanceSnapshotRepo);

            // create file manager
            var fileManager = new DiskFileManager(hostConfig.DiskFileManagerBaseDirectory);

            LongRunningTaskController ctrl = new LongRunningTaskController(
                taskManager: taskManager,
                fileManager: fileManager,
                config: hostConfig);

            ctrl.Request = new HttpRequestMessage();
            ctrl.Request.SetConfiguration(new HttpConfiguration());

            return ctrl;
        }

        private ILongRunningTaskManager CreateTaskManager(ILongRunningTaskInstanceSnapshotRepository taskInstanceSnapshotRepo)
        {
            var config = LongRunningTaskManagerHostConfiguration.Current;

            // file manager
            var fileManager = new DiskFileManager(config.DiskFileManagerBaseDirectory);

            // task result transformer
            var taskResultTransformer = new LongRunningTaskFileResultToFileManagerTransformer(fileManager);

            var taskManager = new LongRunningTaskManager(taskInstanceSnapshotRepo, taskResultTransformer);

            return taskManager;
        }

        private ILongRunningTaskInstanceSnapshotRepository CreateTaskInstanceSnapshotRepo(List<LongRunningTaskInstanceSnapshot> lst)
        {
            // create task instance instance repository
            var taskInstanceSnapshotRepo = new Sustainalytics.LongRunningTaskManager.Interface.Fakes.StubILongRunningTaskInstanceSnapshotRepository
            {
                GetString = (string taskInstanceId) =>
                {
                    var taskInstance = lst.Where(el => el.Id == taskInstanceId).FirstOrDefault();

                    return taskInstance;
                },

                GetAll = () =>
                {
                    return lst.OrderBy(el => el.Order).ToList();
                },

                InsertLongRunningTaskInstanceSnapshot = (LongRunningTaskInstanceSnapshot snapshot) =>
                {
                    lst.Add(snapshot);
                },

                InsertIEnumerableOfLongRunningTaskInstanceSnapshot = (IEnumerable<LongRunningTaskInstanceSnapshot> snapshots) =>
                {
                    lst.AddRange(snapshots);
                },

                DeleteString = (string taskInstanceId) =>
                {
                    var taskInstance = lst.Where(el => el.Id == taskInstanceId).FirstOrDefault();

                    if (taskInstance != null)
                    {
                        lst.Remove(taskInstance);
                    }
                }
            };

            return taskInstanceSnapshotRepo;
        }

        private AuthenticationTicket CreateAuthenticationTicket(string username)
        {
            AuthenticationTicket ticket = new AuthenticationTicket(
                identity: new ClaimsIdentity(
                    claims: new List<Claim> { new Claim(ClaimTypes.Name, username) },
                    authenticationType: "Bearer"),
                properties: new AuthenticationProperties());

            return ticket;
        }
    }
}
